package org.codehaus.activemq.service.impl;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;
import javax.transaction.xa.XAException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.broker.Broker;
import org.codehaus.activemq.message.ActiveMQXid;
import org.codehaus.activemq.service.Transaction;
import org.codehaus.activemq.store.PreparedTransactionStore;
import org.codehaus.activemq.util.SerializationHelper;

public class XATransactionCommand extends AbstractTransaction
{
  private static final Log log = LogFactory.getLog(TransactionManagerImpl.class);
  private ActiveMQXid xid;
  private transient Map xaTxs;
  private transient PreparedTransactionStore preparedTransactions;

  public XATransactionCommand()
  {
    super(null);
  }

  public XATransactionCommand(Broker broker, ActiveMQXid xid, Map xaTxs, PreparedTransactionStore preparedTransactions) {
    super(broker);
    this.xid = xid;
    this.xaTxs = xaTxs;
    this.preparedTransactions = preparedTransactions;
  }

  public void initialise(Map xaTxs, PreparedTransactionStore preparedTransactions)
  {
    this.xaTxs = xaTxs;
    this.preparedTransactions = preparedTransactions;
  }

  public void commit(boolean onePhase) throws XAException {
    switch (getState())
    {
    case 0:
      if (!onePhase) {
        XAException xae = new XAException("Cannot do 2 phase commit if the transaction has not been prepared.");
        xae.errorCode = -6;
        throw xae;
      }
      setState((byte)3);
      this.xaTxs.remove(this.xid);
      break;
    case 1:
      if (!onePhase) {
        XAException xae = new XAException("Cannot do 2 phase commit if the transaction has not been prepared.");
        xae.errorCode = -6;
        throw xae;
      }
      try
      {
        prePrepare();
      }
      catch (XAException e) {
        throw e;
      }
      catch (Throwable e) {
        log.warn("COMMIT FAILED: ", e);
        rollback();

        XAException xae = new XAException("ONE PHASE COMMIT FAILED: Transaction rolled back.");
        xae.errorCode = 104;
        xae.initCause(e);
        throw xae;
      }

      setState((byte)3);
      this.xaTxs.remove(this.xid);
      try
      {
        postCommit();
      }
      catch (Throwable e)
      {
        log.warn("POST COMMIT FAILED: ", e);
        XAException xae = new XAException("POST COMMIT FAILED");
        xae.errorCode = -3;
        xae.initCause(e);
        throw xae;
      }

    case 2:
      setState((byte)3);
      this.xaTxs.remove(this.xid);
      try
      {
        postCommit();
      }
      catch (Throwable e)
      {
        log.warn("POST COMMIT FAILED: ", e);
        XAException xae = new XAException("POST COMMIT FAILED");
        xae.errorCode = -3;
        xae.initCause(e);
        throw xae;
      }

    default:
      XAException xae = new XAException("Cannot call commit now.");
      xae.errorCode = -6;
      throw xae;
    }
  }

  public void rollback() throws XAException
  {
    switch (getState())
    {
    case 0:
      this.xaTxs.remove(this.xid);
      setState((byte)3);

      break;
    case 1:
      this.xaTxs.remove(this.xid);
      setState((byte)3);
      try
      {
        postRollback();
      }
      catch (Throwable e)
      {
        log.warn("POST ROLLBACK FAILED: ", e);
        XAException xae = new XAException("POST ROLLBACK FAILED");
        xae.errorCode = -3;
        xae.initCause(e);
        throw xae;
      }

    case 2:
      this.preparedTransactions.remove(this.xid);
      this.xaTxs.remove(this.xid);
      setState((byte)3);
      try
      {
        postRollback();
      }
      catch (Throwable e)
      {
        log.warn("POST ROLLBACK FAILED: ", e);
        XAException xae = new XAException("POST ROLLBACK FAILED");
        xae.errorCode = -3;
        xae.initCause(e);
        throw xae;
      }
    }
  }

  public int prepare()
    throws XAException
  {
    switch (getState())
    {
    case 0:
      this.xaTxs.remove(this.xid);
      setState((byte)3);
      return 3;
    case 1:
      try
      {
        prePrepare();
      }
      catch (XAException e) {
        throw e;
      }
      catch (Throwable e) {
        log.warn("PREPARE FAILED: ", e);
        rollback();

        XAException xae = new XAException("PREPARE FAILED: Transaction rollback.");
        xae.errorCode = 104;
        xae.initCause(e);
        throw xae;
      }

      setState((byte)2);
      this.preparedTransactions.put(this.xid, this);

      return 0;
    }
    XAException xae = new XAException("Cannot call prepare now.");
    xae.errorCode = -6;
    throw xae;
  }

  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
  {
    super.readExternal(in);
    this.xid = ActiveMQXid.read(in);
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    super.writeExternal(out);
    this.xid.writeExternal(out);
  }

  public static Transaction fromBytes(byte[] data) throws IOException, ClassNotFoundException {
    return (Transaction)SerializationHelper.deserialize(data);
  }

  public byte[] toBytes() throws IOException {
    return SerializationHelper.serialize(this);
  }
}